package com.hy.proxy.core;

import com.hy.proxy.common.LogUtils;
import com.hy.proxy.common.ThreadPoolUtils;
import com.hy.proxy.core.entity.*;
import com.hy.proxy.core.entity.automate.CssPath;
import com.hy.proxy.core.entity.automate.Request;
import com.hy.proxy.core.entity.automate.Site;
import com.hy.proxy.core.util.ProxyUtils;
import com.hy.proxy.core.util.SeleniumUtils;
import lombok.extern.slf4j.Slf4j;
import net.sf.json.JSONObject;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.openqa.selenium.chrome.ChromeDriver;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;

/**
 * @Author: hy
 * @Date: Created in 2019/10/18/17:02
 * @Description:
 * @Version: 1.0
 */
@Slf4j
public class AutomateProxyTool {

    /**
     * 全局网站列表
     */
    private static List<Site> siteList;

    /**
     * 公用 WebDriver
     * 一个tab为一个网站，当请求完成后则关闭这个tab，不会在打开新浏览器了
     */
    private static ChromeDriver chromeDriver;

    /**
     * 启动自动查询
     */
    public static void start() {
        try {

            Config config = new Config();
            List<Site> siteList = config.getSiteList();

            CountDownLatch countDownLatch = new CountDownLatch(siteList.size());
            //
//            List<String> windowHandleList = new ArrayList<>(chromeDriver.getWindowHandles());

            //
            for (Site site : siteList) {

                if (site.getQueryMode() == Site.QueryMode.REQUEST) {
                    ThreadPoolUtils.Instance.executor(() -> {
                        requestMode(site);
                        countDownLatch.countDown();
                    });
                } else {
                    ThreadPoolUtils.Instance.executor(() -> {
                        cssPathMode(site);
                        countDownLatch.countDown();
                    });
                }
            }

            //开始计数，锁定主线程
            countDownLatch.await();

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            ThreadPoolUtils.Instance.shutdown();
        }

    }

    /**
     * Request模式查询
     *
     * @param site
     */
    public static void requestMode(Site site) {

        ChromeDriver chromeDriver = SeleniumUtils.getChromeDriver();

        //获取RequestMode的列表
        List<Request> requestList = site.getRequestList();

        //需要获取getAddress()源码
        if (site.isUseRequestAddress()) {
            getSiteSource(site.getAddress(), site.getLibChoose(), chromeDriver);
        }

        //发送请求
        for (Request request : requestList) {

            getSiteSource(request.getUrl(), site.getLibChoose(), chromeDriver);

            //获取请求返回的数据
            String json = chromeDriver.findElementByTagName("body").getText();

            System.out.println(json);

            //构建一个Json对象
            JSONObject jsonObject = null;
            try {
                jsonObject = JSONObject.fromObject(json);

                //获取
                String jsonAttrObject = jsonObject.get(request.getJsonAttrName()).toString();

                //生成对象
                List<Proxy> proxyList = ProxyUtils.toProxyListByJson(request.getReturnType(), jsonAttrObject);

                //存入
                GlobalObject.Instance.add(proxyList);

            } catch (Exception e) {
                log.error(LogUtils.getCurrentMethodName() + ": 请求 " + request.getUrl() + " 解析JSON时出现错误! " + e.getMessage());
            }

        }

        //关闭当前窗口
//        chromeDriver.close();

//        if (windowHandleList.size() != (siteList.size() - 1)) {
//
//            windowHandleList.clear();
//            windowHandleList.addAll(chromeDriver.getWindowHandles());
//
//            //打开一个新的Tab
//            //使用Async的会导致程序卡死
//            chromeDriver.executeScript("open()");
//
//            try {
//                Thread.sleep(1000);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//
//            chromeDriver.switchTo().window(windowHandleList.get(windowHandleList.size() - 1));
//
//        }

    }

    /**
     * CssPath模式查询
     *
     * @param site
     */
    public static void cssPathMode(Site site) {


        Element body;

        //使用cssPath模式的一定需要请求getAddress();
        //如果CssPath模式使用的Lib是SELENIUM就重新创建一个
        if (Site.Lib.SELENIUM == site.getLibChoose()) {
            ChromeDriver chromeDriver = SeleniumUtils.getChromeDriver();
            body = getSiteSource(site.getAddress(), site.getLibChoose(), chromeDriver);
        } else {
            body = getSiteSource(site.getAddress(), site.getLibChoose());
        }

        Map<String, CssPath> cssPath = site.getCssPath();

        //使用entrySet的效率要比遍历Key和value高，省去了get获取数据的操作
        //遍历CssPathMap的Key获取CssPath对象
        for (Map.Entry<String, CssPath> cssPathEntry : cssPath.entrySet()) {

            //获取路径查询
            int[] indexArray = cssPathEntry.getValue().getIndexArray();

            //获取css查询路径
            List<String> pathList = cssPathEntry.getValue().getPathList();

            //使用PathList的第一个css先获取一次原始数据
            Elements elements = body.select(pathList.get(0));

            //长度为1时只有一个base64加密链接
            if (indexArray.length == 1) {
                Element element = elements.get(indexArray[0]);
                base64(cssPathEntry.getValue().getIndexArrayAttr()[0], element);
                continue;
            }

            //多层css路径(有些特殊的网站只有分享一个proxy)
            if (pathList.size() != 1) {
                //使用原始数据遍历PathList中的其他css获取最终数据
                List<Elements> elementsList = forPathList(elements, pathList);
                elementsList.forEach((e) -> createProxy(Proxy.ProxyType.valueOf(cssPathEntry.getKey().toUpperCase()), indexArray, e));
            } else {
                createProxy(Proxy.ProxyType.valueOf(cssPathEntry.getKey().toUpperCase()), indexArray, elements);
            }
        }

    }

    /**
     * 遍历PathList获取最终数据
     *
     * @param elements
     * @param pathList
     * @return
     */
    private static List<Elements> forPathList(Elements elements, List<String> pathList) {

        List<Elements> elementsList = new ArrayList<>();

        //从第二层css路径开始遍历
        for (int i = 1; i < pathList.size(); i++) {

            //Todo 一般都是2层，如果有第三层的逻辑还没做
            for (Element element : elements) {
                Elements select = element.select(pathList.get(i));
                elementsList.add(select);
            }

        }
        return elementsList;
    }

    /**
     * 根剧ProxyType创建一个对象
     *
     * @param proxyType
     * @param e         对象的属性
     * @return
     */
    public static Proxy createProxy(Proxy.ProxyType proxyType, int[] indexArray, Elements e) {

        Proxy proxy;

        if (proxyType == Proxy.ProxyType.SSR) {
            proxy = new SSR();

            //必须
            ((SSR) proxy).setServer(e.get(indexArray[0]).text());
            ((SSR) proxy).setServerPort(Integer.parseInt(e.get(indexArray[1]).text()));
            ((SSR) proxy).setProtocol(e.get(indexArray[2]).text());
            ((SSR) proxy).setMethod(e.get(indexArray[3]).text());
            ((SSR) proxy).setObfs(e.get(indexArray[4]).text());
            ((SSR) proxy).setPassword(e.get(indexArray[5]).text());

            //可选
            ((SSR) proxy).setObfsParam(e.get(indexArray[6]).text());
            ((SSR) proxy).setProtocolParam(e.get(indexArray[7]).text());
            ((SSR) proxy).setRemarks(e.get(indexArray[8]).text());
            ((SSR) proxy).setGroup(e.get(indexArray[9]).text());

            GlobalObject.Instance.getSsrList().add(proxy);
        } else if (proxyType == Proxy.ProxyType.V2RAY) {
            proxy = new V2ray();
            ((V2ray) proxy).setAdd(e.get(indexArray[0]).text());
            ((V2ray) proxy).setPort(e.get(indexArray[1]).text());
            ((V2ray) proxy).setId(e.get(indexArray[2]).text());
            ((V2ray) proxy).setNet(e.get(indexArray[3]).text());
            ((V2ray) proxy).setHost(e.get(indexArray[4]).text());
            ((V2ray) proxy).setTls(e.get(indexArray[5]).text());
            GlobalObject.Instance.getV2rayList().add(proxy);
        } else {
            proxy = new SS();
            ((SS) proxy).setMethod(e.get(indexArray[0]).text());
            ((SS) proxy).setPassword(e.get(indexArray[1]).text());
            ((SS) proxy).setServer(e.get(indexArray[2]).text());
            ((SS) proxy).setServerPort(Integer.parseInt(e.get(indexArray[3]).text()));
            GlobalObject.Instance.getSsList().add(proxy);
        }
        return proxy;

    }

    /**
     * 从Html属性中获取Proxy链接并生成对象
     *
     * @param attrName 属性名称
     * @param element  包含该属性的节点
     */
    public static Proxy base64(String attrName, Element element) {

        //获取对应属性的值
        String base64Str = element.attr(attrName);
        //解密链接
        Proxy proxy = ProxyUtils.decoderProxyLink(base64Str);
        //添加到列表中
        GlobalObject.Instance.add(proxy);

        return proxy;
    }


    /**
     * 获取网站源码
     *
     * @param address 网址
     * @param lib     使用的工具
     * @return
     */
    public static Element getSiteSource(String address, Site.Lib lib, ChromeDriver chromeDriver) {

        Element body;

        if (lib == Site.Lib.JSOUP) {
            body = getSiteSourceByJsoup(address);
        } else {
            body = getSiteSourceBySelenium(address, chromeDriver);
        }
        return body;
    }

    /**
     * 获取网站源码，使用公有的 chromeDriver
     *
     * @param address
     * @param lib
     * @return
     */
    public static Element getSiteSource(String address, Site.Lib lib) {
        return getSiteSource(address, lib, chromeDriver);
    }

    private static Element getSiteSourceByJsoup(String address) {

        Element body = null;

        //获取网站地址源码
        try {

            body = Jsoup
                    .connect(address)
                    .timeout(500000)
                    .get()
                    .body();

        } catch (IOException e) {
            e.printStackTrace();
        }

        return body;
    }

    private static Element getSiteSourceBySelenium(String address, ChromeDriver chromeDriver) {
        String source = SeleniumUtils.getPageSource(chromeDriver, address);
        return Jsoup.parse(source).body();
    }
}
